#include "conv2.h"

using namespace std;

// prototypes
void createCPUdata(int argc, char** argv);
void initGLUT(int argc, char** argv);
void initGLEW();
void set1to1Mapping();  // sets the projection for 1:1 mapping
void initGPUTextures(); // set up texturs on gpu
void initCgProg();      // initialize the Cg shader program
void copyTexturesToGPU(); // copy CPU data to gpu texture
void printInputData();
void printResults();
void printResultstoFile(string fileName);
void render();
void readBacktoCPU(int texType);
void initFBO(void);
void usage();
void printOptionsSummary();
void printProgDesc();
void getCommandArgs(int argc, char** argv);
void attachTexturesToFBO();
bool checkFramebufferStatus();

void checkGLErrors (const char *label);

void createTextures();
void setupTexture(const GLuint texID, GLint xSize, GLint ySize);
void transferToTexture(float *data, GLuint texID, GLint xSize, GLint ySize);



/***********************************************************
 * MAIN
 ***********************************************************/
int main(int argc, char **argv)
{
   time(&rawTime);
   timeInfo = localtime(&rawTime);
   cout << asctime(timeInfo);

   if (argc != 2) {
      usage();
      exit(1);
   }

   int day   = timeInfo->tm_mday;
   int month = timeInfo->tm_mon;
   int hour  = timeInfo->tm_hour;
   int min   = timeInfo->tm_min;
   int sec   = timeInfo->tm_sec;
   int year  = timeInfo->tm_year + 1900;

   cout << sec << min << hour << "-" << day << month << year << endl;

   printProgDesc();
   cout << "Status: uid: " << getuid() << endl;
   cout << "Status: pid: " << getpid() << endl;


   createCPUdata(argc, argv);
   initGLUT(argc,argv);
   initGLEW();

   initFBO();
   set1to1Mapping();
   createTextures();

   attachTexturesToFBO();
   //copyTexturesToGPU();

   printInputData();
   readBacktoCPU(readTex);
   printResults();


   initCgProg();
   render();
   readBacktoCPU(writeTex);
   printResults();

   if (atoi(argv[1]) == 1) {
      stringstream convFileName;
      convFileName <<  "conv-"
           << sec << min << hour
           << "-" << day << month << year;

      printResultstoFile(convFileName.str());
   }

   // clean up
   free(readTexArr);
   free(writeTexArr);
   free(convFuncTexArr);
   free(resultArr);

   glDeleteTextures(3,d_textures);
   cgDestroyContext(cgContext);

   return 0;
} // main()









void getCommandArgs(int argc, char** argv)
/*********************************************************
 * DESC: Retrieves the command line arguments
 *
 *
 *********************************************************/
{
   if (argc != 1) {
      usage();
      exit(1);
   }

   //width = atoi(argv[1]);
   //height = atoi(argv[2]);

} // getCommandArgs()


void createCPUdata(int argc, char **argv)
/*********************************************************
 * DESC: create data arrays on the GPU as input for the
 *       textures on the GPU
 *********************************************************/
{
   cout << "Status: Creating CPU data ...." << flush << endl;

   cout << "Status: length of array allocated: "
    <<  dataHeight*dataWidth*4 << endl;

   readTexArr     = (GLfloat*)malloc(dataHeight*dataWidth*4*sizeof(GLfloat));
   writeTexArr     = (GLfloat*)malloc(dataHeight*dataWidth*4*sizeof(GLfloat));
   convFuncTexArr = (GLfloat*)malloc(convHeight*convWidth*4*sizeof(GLfloat));
   resultArr      = (GLfloat*)malloc(dataHeight*dataWidth*4*sizeof(GLfloat));

   int index = 0;
   GLfloat colour = 0.0;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataWidth*i)+j);
     readTexArr[index]   = colour + 1.0;
     readTexArr[index+1] = colour + 2.0;
     readTexArr[index+2] = colour + 3.0;
     readTexArr[index+3] = colour + 4.0;
      }
   }

   index = 0;
   colour = 99.0;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataWidth*i)+j);
     writeTexArr[index]   = colour;
     writeTexArr[index+1] = colour;
     writeTexArr[index+2] = colour;
     writeTexArr[index+3] = colour;
      }
   }

   index = 0;
   colour = 0.5;
   for (int i=0;i<convHeight; i++) {
      for (int j=0;j<convWidth;j++) {
     index = 4*((convWidth*i)+j);
     convFuncTexArr[index]  = colour;
      convFuncTexArr[index+1] = colour + 1.0;
      convFuncTexArr[index+2] = colour + 2.0;
      convFuncTexArr[index+3] = colour + 3.0;
       }
   }

   index = 0;
   colour = -99.0;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataWidth*i)+j);
     resultArr[index]   = colour;
      resultArr[index+1] = colour;
      resultArr[index+2] = colour;
      resultArr[index+3] = colour;
       }
   }

   cout << "Status: done" << endl;
   return;

} // createCPUdata()




void initGLUT(int argc, char** argv)
/*********************************************************
 * DESC: Initialize glut to set up a valid openGL context
 *
 *********************************************************/
{
   glutInit(&argc, argv);
   glutCreateWindow("TEST1");
}



void initGLEW()
/*********************************************************
 * DESC: Initialise the openGL extension extry points.
 *       This also checks if the initialization has suceeded
 *       and checks if the extension GLEW_ARB_fragment_program
 *       is availiable.
 *********************************************************/
{
   // init GLEW, obtain function pointers for extensions
   GLenum err = glewInit();
   // Warning this does not check if all extensions used
   // in a given implimentaion are actually supported.
   // Fuction extrey points created by glewInit() will be
   // NULL in that case!
   if (GLEW_OK != err) {
      // Problem: glewInit failed, somthing is seriously wrong.
      cerr << "Error: " << glewGetErrorString(err) << endl;
      exit (ERROR_GLEW);
   }
   // return the GLEW version string
   cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << endl;


   // check if a particular extention is avaliable.
   // note: in getneral use GLEW_{extension_name}
   if (GLEW_ARB_fragment_program) {
      cout << "Status: Extention, GLEW_ARB_fragment_program is availiable"
       << endl;
  } else {
      cerr << "Error: Extention, GLEW_ARB_fragment_program is missing"
       << endl;
      exit (ERROR_GLEW);
   }

   return;

} // initGLEW()




void set1to1Mapping()
/*********************************************************
 * DESC: set the projection matrices such that we get
 *       1:1 mapping between pixels,texels and data when
 *       rendering.
 *
 *********************************************************/
{
   // viewport transform for 1:1 pixel=texel=data mapping
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0,dataWidth,0.0,dataHeight); // (left,right,bottom,top)
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glViewport(0,0,dataWidth,dataHeight);
}


void initGPUTextures()
/*********************************************************
 * DESC: generate and initialise textures on the GPU
 *       This creates an unclamped (allowing numbers
 *       outside the range (0-225) floating point
 *       texture. To get fp textures it is necessary to use
 *       an offscreen buffer or a texture attached to an
 *       FBO. It is important that the correct texture target
 *       and internal texture formats are chosen
 * NOTE: This function creates two textures, one for the
 *       data and one for the convolution kernel
 *
 *********************************************************/
{
   // create textures (this creates 2 textures and stores the names as
   // textures[0] and textures[1]
   glGenTextures (3, d_textures);

   // read (data) Array Texture
   // -----------------------------------------------------------
   glBindTexture(textarget,d_textures[readTex]);

   // set texture parameters
   // this assumes the textures are already generated and bound
   // with glGenTextures()  and ...
   // glBindTexture()
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);

   // define texture with floating point format
   // note the last argument being 0 creates the texture but
   // does not copy data to it
   glTexImage2D(textarget,0,texinternalformat,dataWidth,
        dataHeight,0,GL_RGBA,GL_FLOAT,0);
   //   glEnable(GL_TEXTURE_RECTANGLE_ARB);


  // read (data) Array Texture
   // -----------------------------------------------------------
   glBindTexture(textarget,d_textures[writeTex]);

   // set texture parameters
   // this assumes the textures are already generated and bound
   // with glGenTextures()  and ...
   // glBindTexture()
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);

   // define texture with floating point format
   // note the last argument being 0 creates the texture but
   // does not copy data to it
   glTexImage2D(textarget,0,texinternalformat,dataWidth,
        dataHeight,0,GL_RGBA,GL_FLOAT,0);
   //   glEnable(GL_TEXTURE_RECTANGLE_ARB);


   // conv kerel Array Texture
   // -----------------------------------------------------------
//    glBindTexture(textarget,textures[convFuncTex]);
//    glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
//    glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//    glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
//    glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
//    glTexImage2D(textarget,
// 		0,
// 		texinternalformat,
// 		convWidth,
// 		convHeight,
// 		0,
// 		GL_RGBA,
// 		GL_FLOAT,
// 		//0);
// 		convFuncTexArr);

   return;
}




void initCgProg()
{
   // set up Cg

   // create the context - this is a container for cg programs
   cgContext = cgCreateContext();
   if (cgContext == 0) {
    cerr << "Cg Context creation failure" << endl;
    exit(-1);
   }

   cgFragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);
   if (cgFragmentProfile == CG_PROFILE_UNKNOWN) {
    cerr << "Invalid Fragment Program profile" << endl;
    exit(-1);
   }
   cgGLSetOptimalOptions(cgFragmentProfile);

   const char* programSource = "kernels/conv.frag";
   const char* programEntry  = "convFunc";

   // create fragment program
   cgProgram  = cgCreateProgramFromFile(
        cgContext, // the contex where we want the shader added
        CG_SOURCE, // array of bytes containing the cg source
        programSource, // the source file (char *)
        cgFragmentProfile, // the profile
        programEntry,            // the entry function
        NULL);           // compiler options

   if (cgProgram == 0) {
    CGerror err = cgGetError();
    cerr << cgGetErrorString(err) << endl;
    exit(-1);
   }
   // load program
   cgGLLoadProgram(cgProgram);



}


void attachTexturesToFBO()
/*********************************************************
 * DESC: attach textures to the FBO
 *
 *********************************************************/
{
   glEnable(textarget);

   //   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
   //			     source[writeTex],
   //			     textarget,
   //			     textureID[writeTex],
   //			     0);

   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
               texAttachment[readTex],
               textarget,
               d_textures[readTex],
               0);

   cout << endl;
   if (!checkFramebufferStatus()) {
      printf("attach FBO status: glFramebufferTexture2DEXT():\t [FAIL]\n");
      exit (ERROR_FBOTEXTURE);
   }

   return;
}






void copyTexturesToGPU()
/*********************************************************
 * DESC:
 *
 *********************************************************/
{
   //cout << source[readTex] << endl;
   glDrawBuffer(texAttachment[readTex]);
   glRasterPos2i(0,0);    // reset the raster postion to (top left?)
   glDrawPixels(dataWidth,    // dimentions of the pixel rectangle to be
        dataHeight,   // written to the frame buffer
        GL_RGBA,  // format of the pixel data
        GL_FLOAT, // data type for pixels
        readTexArr); // pointer to the pixel data


   cout << endl;
   if (!checkFramebufferStatus()) {
      printf("Copy status(read): glFramebufferTexture2DEXT():\t [FAIL]\n");
      exit (ERROR_FBOTEXTURE);
   }


   //cout << source[writeTex] << endl;
   glDrawBuffer(texAttachment[writeTex]);
   glRasterPos2i(0,0);    // reset the raster postion to (top left?)
   glDrawPixels(dataWidth,    // dimentions of the pixel rectangle to be
        dataHeight,   // written to the frame buffer
        GL_RGBA,  // format of the pixel data
        GL_FLOAT, // data type for pixels
        writeTexArr); // pointer to the pixel data

   cout << endl;
   if (!checkFramebufferStatus()) {
      printf("Copy status(write): glFramebufferTexture2DEXT():\t [FAIL]\n");
      exit (ERROR_FBOTEXTURE);
   }



   //cout << source[convFuncTex] << endl;
   //glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,0);
//    glDrawBuffer(source[convFuncTex]);
//    glRasterPos2i(0,0);    // reset the raster postion to (top left?)
//    glDrawPixels(convWidth,    // dimentions of the pixel rectangle to be
// 		convHeight,   // written to the frame buffer
// 		GL_RGBA,  // format of the pixel data
// 		GL_FLOAT, // data type for pixels
//     		convFuncTexArr); // pointer to the pixel data
   //glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);

   cout << endl;
   if (!checkFramebufferStatus()) {
      printf("Copy status(conv): glFramebufferTexture2DEXT():\t [FAIL]\n");
      exit (ERROR_FBOTEXTURE);
   }


   return;
} // copyTexturesToGPU()


void render()
/*********************************************************
 * DESC: render a full screen quad to render and therefore
 *       perform the fragment shader calculation
 *
 *       Cg paramters are also set up here
 *********************************************************/
{
   attachTexturesToFBO();

   // get parameter handles by name
   // gets the named paramter "data" from the Cg fragment shader program
   // and stores it as a CGparamter called textureHandle
   dataHandle     =  cgGetNamedParameter(cgProgram, "dataArr");
   convFuncHandle =  cgGetNamedParameter(cgProgram, "convFuncArr");
   //wParam        =  cgGetNamedParameter(cgProgram, "wTerm");
   //uMaxParam        =  cgGetNamedParameter(cgProgram, "umax");
   //vMaxParam        =  cgGetNamedParameter(cgProgram, "vmax");
   // we later use this handle to set the "data" texture to the openGL
   // texture set up in initGPUtextures() with the texture array name 'texture'

   // enable texture 'data' (read only)
   cgGLSetTextureParameter(dataHandle,d_textures[readTex]);
   cgGLEnableTextureParameter(dataHandle);
   cgGLSetTextureParameter(convFuncHandle,d_textures[convFuncTex]);
   cgGLEnableTextureParameter(convFuncHandle);

   // enable scalar 'vMax'
   //cgSetParameter1f(vMaxParam,vmax);

   // bind fragment program (in this case set up to be ident.frag)
   cgGLBindProgram(cgProgram);
   // enable fragment profile
   cgGLEnableProfile(cgFragmentProfile);

   // make quad filled to hit every pixel/texel
   glPolygonMode(GL_FRONT,GL_FILL);
   // render quad
   glBegin(GL_QUADS);
   glTexCoord2f(0.0, 0.0);
   glVertex2f(0.0, 0.0);
   glTexCoord2f(dataWidth, 0.0);
   glVertex2f(dataWidth, 0.0);
   glTexCoord2f(dataWidth, dataHeight);
   glVertex2f(dataWidth, dataHeight);
   glTexCoord2f(0.0, dataHeight);
   glVertex2f(0.0, dataHeight);
   glEnd();


   cgGLDisableProfile(cgFragmentProfile);

   return;
} // render


void readBacktoCPU(int texType)
{
   // and read back
   switch (texType) {
   case writeTex:
      cout << "Status: reading back write texture" << endl;
      glReadBuffer(texAttachment[writeTex]);
      glReadPixels(0, 0, dataWidth, dataHeight,GL_RGBA,GL_FLOAT,resultArr);
      break;
   case readTex:
      cout << "Status: reading back read texture" << endl;
      glReadBuffer(texAttachment[readTex]);
      glReadPixels(0, 0, dataWidth, dataHeight,GL_RGBA,GL_FLOAT,resultArr);
      break;
   default:
      cout << "Error: attempted to read back an unknown texture" << endl;
      exit(ERROR_READBACK);
   }

      return;
}



void printInputData(){

     cout << "Data before roundtrip:" << endl;
   int index = 0;
   const int printWidth = 10;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataWidth*i)+j);
     cout.precision(4);
     cout << setw(printWidth) << setiosflags(ios::left);
     cout << readTexArr[index] << " ";
     cout << setw(printWidth) << setiosflags(ios::left);
      cout << readTexArr[index+1] << " ";
     cout << setw(printWidth) << setiosflags(ios::left);
      cout << readTexArr[index+2] << " ";
     cout << setw(printWidth) << setiosflags(ios::left);
      cout << readTexArr[index+3] << endl;
       }
       cout << endl;
    }

   cout << endl;



   return;
}

void printResults()
{
   cout << "Data after render: " << endl;
   //cout << "[(coord.y) (coord.x) (w) (cos(pi*w*(coord.x^2+coord.y^2))]" << endl;
   int index = 0;
   const int printWidth = 10;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataWidth*i)+j);
     cout.precision(4);
     cout << setw(printWidth) << setiosflags(ios::left);
     cout << resultArr[index+1];
     cout << setw(printWidth) << setiosflags(ios::left);
     cout << resultArr[index];
     cout << setw(printWidth) << setiosflags(ios::left);
     cout << resultArr[index+2];;
     cout << setw(printWidth) << setiosflags(ios::left);
     cout << resultArr[index+3] << endl;
      }
      cout << endl;
   }
}





void initFBO(void)
/**********************************************************
 * DESC: Enable the openGL framebuffer extention providing
 *       full 32bit precision support and removing all
 *       unwanted clamping issues. This function creates the
 *       off-screen buffer and binds it to the framebuffer
 *       extention
 **********************************************************/
{
   // Create FB0 (off-screen frame buffer)
   //   note: that binding FBO to number 0 will restore
   //   the window-system specific framebuffer at any time.
   glGenFramebuffersEXT(1, &fb); // (number of buffers to generate
   //                                       ,array array of enumerants)

   // bind offscreen buffer to fb -> this changes were we are rendering to
   glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);

   return;

} // initFBO()



void usage() {
/*********************************************************
 * DESC: Prints out usage information to the command line
 *
 *********************************************************/
   cout << endl;
   cout << "USAGE: ./ATItest5 [output val]" << endl;
   cout << " output val -> 0 = no output files" << endl;
   cout << " output val -> 1 = write output files" << endl;
   cout << endl;

   return;
} // usage()



void printResultstoFile(string fileName) {
/*********************************************************
 * DESC: Prints out results of GPU calcualtions to a file
 *       after the textures have been coppied back to the
 *       CPU.
 * ARGS: (string fileName) the filename of the data file
 *       to be written. In the present form this is used as
 *       the base filename
 *********************************************************/
   ofstream outFile_Re;
   ofstream outFile_Im;

   string sOutfile_Re = fileName+".Re";
   string sOutfile_Im = fileName+".Im";

   const char* cOutfile_Re = sOutfile_Re.c_str();
   const char* cOutfile_Im = sOutfile_Im.c_str();

   string messageString1 =
      "Writing w function (real part) to:  ("+sOutfile_Re+") ";
   string messageString2 =
      "Writing w function (imag part) to:  ("+sOutfile_Im+") ";

   cout << "--------------------------------------------------" << endl;
   cout << "- " << messageString1 << endl;
   cout << "- " << messageString2 << endl;
   cout << "--------------------------------------------------" << endl;

   outFile_Re.open(cOutfile_Re);
   outFile_Im.open(cOutfile_Im);

   int index=0;
   const int printWidth = 15;
   for (int i=0;i<dataHeight; i++) {
      for (int j=0;j<dataWidth;j++) {
     index = 4*((dataHeight*i)+j);
     //outFile.precision(4);

     outFile_Re << setw(printWidth) << setiosflags(ios::left);
     outFile_Re << resultArr[index+1] << "\t";
     outFile_Re << setw(printWidth) << setiosflags(ios::left);
     outFile_Re << resultArr[index] << "\t";
     outFile_Re << setw(printWidth) << setiosflags(ios::left);
     outFile_Re << resultArr[index+2] << endl;

     outFile_Im << setw(printWidth) << setiosflags(ios::left);
     outFile_Im << resultArr[index+1] << "\t";
     outFile_Im << setw(printWidth) << setiosflags(ios::left);
     outFile_Im << resultArr[index] << "\t";
     outFile_Im << setw(printWidth) << setiosflags(ios::left);
     outFile_Im << resultArr[index+3] << endl;

      }
      outFile_Re << endl;
      outFile_Im << endl;

   }

   outFile_Re.close();
   outFile_Im.close();



   return;

} // printResultsToFile()



void printOptionsSummary()
/*********************************************************
 * DESC: Prints out a summary of the command line options
 *       to the stdout
 *********************************************************/
{
   cout << endl;
   cout << "--------------------------------------------------" << endl;
   cout << "Command line options: "                             << endl;
   cout << " -Width   = " << dataWidth                          << endl;
   cout << " -Height  = " << dataHeight                         << endl;
   cout << "--------------------------------------------------" << endl;
   cout << endl;

} // printOptionsSummary()



void printProgDesc()
/*********************************************************
 * DESC: Prints a description of the program to stdout
 *
 *********************************************************/
{
   cout << "==================================================" << endl;
   cout << "     Program to perform convolution on the GPU    " << endl;
   cout << "==================================================" << endl;

   return;
}




/**
 * Checks framebuffer status.
 * Copied directly out of the spec, modified to deliver a return value.
 */
bool checkFramebufferStatus() {
   GLenum status;
   status = (GLenum) glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
   switch(status) {
   case GL_FRAMEBUFFER_COMPLETE_EXT:
      return true;
   case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
      printf("Framebuffer incomplete, incomplete attachment\n");
      return false;
   case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
      printf("Unsupported framebuffer format\n");
      return false;
   case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
      printf("Framebuffer incomplete, missing attachment\n");
      return false;
   case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
      printf("Framebuffer incomplete, attached images must have same dimensions\n");
      return false;
   case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
      printf("Framebuffer incomplete, attached images must have same format\n");
      return false;
   case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
      printf("Framebuffer incomplete, missing draw buffer\n");
      return false;
   case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
      printf("Framebuffer incomplete, missing read buffer\n");
      return false;
   }
   return false;
}


/**
 * Checks for OpenGL errors.
 * Extremely useful debugging function: When developing,
 * make sure to call this after almost every GL call.
 */
void checkGLErrors (const char *label) {
    GLenum errCode;
    const GLubyte *errStr;

    if ((errCode = glGetError()) != GL_NO_ERROR) {
       errStr = gluErrorString(errCode);
        printf("OpenGL ERROR: ");
        printf("%s", (char*)errStr);
        printf("(Label: ");
        printf("%s", label);
        printf(")\n.");
    }
}



void setupTexture(const GLuint texID, GLint xtexSize, GLint ytexSize)
{
   // make active and bind
   glBindTexture(textarget,texID);
   // turn off filtering and wrapping modes
   glTexParameteri(textarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_S, GL_CLAMP);
   glTexParameteri(textarget, GL_TEXTURE_WRAP_T, GL_CLAMP);
   // define the texture with fp format
   glTexImage2D(textarget,0,texinternalformat,xtexSize,
        ytexSize,0,GL_RGBA,GL_FLOAT,0);
   // check if that worked
   if (glGetError() != GL_NO_ERROR) {
      printf("glTexImage2D():\t\t\t [FAIL]\n");
      exit (ERROR_TEXTURE);
   }
   printf("Created a %i by %i floating point texture.\n",xtexSize,ytexSize);
   return;
}



void transferToTexture(float *data, GLuint texID, GLint xSize, GLint ySize)
{
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                 textarget, texID, 0);

   glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
   glRasterPos2i(0,0);
   glDrawPixels(xSize,ySize,GL_RGBA,GL_FLOAT,data);
   glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                 textarget, 0, 0);
   return;
}


void createTextures()
{
   glGenTextures(3,d_textures);
   setupTexture(d_textures[readTex],dataWidth,dataHeight);
   transferToTexture(readTexArr,d_textures[readTex],dataWidth,dataHeight);

   setupTexture(d_textures[writeTex],dataWidth,dataHeight);
   transferToTexture(writeTexArr,d_textures[writeTex],dataWidth,dataHeight);

   setupTexture(d_textures[convFuncTex],convWidth,convHeight);
   transferToTexture(convFuncTexArr,d_textures[convFuncTex],
             convWidth,convHeight);

   // set texenv mode from modulate (the default) to replace)
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
   // check if something went completely wrong
   checkGLErrors("createFBOandTextures()");
   return;
}



